﻿using System.Collections.Generic;

namespace PerfAssist.MemoryProfilerAdvanced.Editor.PAContrib
{
    public enum eDiffStatus
    {
        Added,
        Increased,
        Unchanged,
        Decreased,
        Removed
    }

    public class MemObjectInfoSet
    {
        public static readonly string[] Prefixes =
            {"{added} ", "{increased} ", "{unchanged} ", "{decreased} ", "{removed} "};

        private readonly List<MemObject> _gchandles = new List<MemObject>();
        private readonly Dictionary<ulong, MemObject> _managedObjects = new Dictionary<ulong, MemObject>();

        private readonly Dictionary<int, MemObject> _nativeObjects = new Dictionary<int, MemObject>();
        private readonly List<MemObject> _staticFields = new List<MemObject>();

        public MemObjectInfoSet(List<object> objList2nd)
        {
            foreach (var obj in objList2nd)
            {
                var mo = obj as MemObject;
                if (mo != null)
                {
                    if (mo._thing is NativeUnityEngineObject)
                    {
                        var n = mo._thing as NativeUnityEngineObject;
                        _nativeObjects.Add(n.instanceID, mo);
                    }
                    else if (mo._thing is ManagedObject)
                    {
                        var m = mo._thing as ManagedObject;
                        _managedObjects.Add(m.address, mo);
                    }
                    else if (mo._thing is GCHandle)
                    {
                        _gchandles.Add(mo);
                    }
                    else if (mo._thing is StaticFields)
                    {
                        _staticFields.Add(mo);
                    }
                }
            }
        }

        public static List<object> Diff(List<object> obj1st, List<object> obj2nd)
        {
            var set1st = new MemObjectInfoSet(obj1st);
            var set2nd = new MemObjectInfoSet(obj2nd);

            var nativeOnes = DiffNative(set1st._nativeObjects, set2nd._nativeObjects);
            var managedOnes = DiffManaged(set1st._managedObjects, set2nd._managedObjects);

            var ret = new List<object>();
            ret.AddRange(nativeOnes);
            ret.AddRange(managedOnes);
            return ret;
        }

        private static List<object> DiffNative(Dictionary<int, MemObject> native1st, Dictionary<int, MemObject> native2nd)
        {
            var both = Intersect(native1st, native2nd);

            var ret = new List<object>();

            foreach (var p in native1st)
                if (!both.Contains(p.Key))
                {
                    MarkStatus(p.Value, eDiffStatus.Removed);
                    ret.Add(p.Value);
                }

            foreach (var p in native2nd)
                if (!both.Contains(p.Key))
                {
                    MarkStatus(p.Value, eDiffStatus.Added);
                    ret.Add(p.Value);
                }

            foreach (var i in both)
            {
                var obj1 = native1st[i]._thing as NativeUnityEngineObject;
                var obj2 = native2nd[i]._thing as NativeUnityEngineObject;

                var mo = native2nd[i];
                if (obj1.size == obj2.size)
                {
                    MarkStatus(mo, eDiffStatus.Unchanged);
                }
                else if (obj1.size > obj2.size)
                {
                    MarkStatus(mo, eDiffStatus.Decreased);
                    ret.Add(mo);
                }
                else
                {
                    MarkStatus(mo, eDiffStatus.Increased);
                    ret.Add(mo);
                }
            }

            return ret;
        }

        private static List<object> DiffManaged(Dictionary<ulong, MemObject> managed1st,
            Dictionary<ulong, MemObject> managed2nd)
        {
            var both = Intersect(managed1st, managed2nd);

            var ret = new List<object>();

            foreach (var p in managed1st)
                if (!both.Contains(p.Key))
                {
                    MarkStatus(p.Value, eDiffStatus.Removed);
                    ret.Add(p.Value);
                }

            foreach (var p in managed2nd)
                if (!both.Contains(p.Key))
                {
                    MarkStatus(p.Value, eDiffStatus.Added);
                    ret.Add(p.Value);
                }

            foreach (var i in both)
            {
                var obj1 = managed1st[i]._thing as ManagedObject;
                var obj2 = managed2nd[i]._thing as ManagedObject;

                var mo = managed2nd[i];
                if (obj1.size == obj2.size)
                {
                    MarkStatus(mo, eDiffStatus.Unchanged);
                }
                else if (obj1.size > obj2.size)
                {
                    MarkStatus(mo, eDiffStatus.Decreased);
                    ret.Add(mo);
                }
                else
                {
                    MarkStatus(mo, eDiffStatus.Increased);
                    ret.Add(mo);
                }
            }

            return ret;
        }

        private static HashSet<T> Intersect<T>(Dictionary<T, MemObject> d1, Dictionary<T, MemObject> d2)
        {
            var both = new HashSet<T>();
            foreach (var k in d1.Keys)
                if (d2.ContainsKey(k))
                    both.Add(k);
            return both;
        }

        private static void MarkStatus(MemObject mo, eDiffStatus status)
        {
            mo.InstanceName = Prefixes[(int) status] + mo.InstanceName;
        }
    }
}